﻿/*
 * TokyoTyrant.NET (http://tokyotyrant.codeplex.com)
 * Pure .NET interface to Tokyo Tyrant
 * Copyright (C) 2009 Russell van der Walt (http://blog.ruski.co.za)

 * TokyoTyrant.NET is free software; you can redistribute it and/or modify it under the terms of
 * the Microsoft Public License (Ms-PL). TokyoTyrant.NET is distributed in the hope
 * that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the Microsoft Public
 * License for more details.
 * 
 * Tokyo Cabinet and Tokyo Tyrant is Copyright (C) 2006-2008 Mikio Hirabayashi 
 * http://tokyocabinet.sourceforge.net/
 * 
 */


using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.IO;
using System.Net;
using System.Net.Sockets;

namespace TokyoTyrant.NET {

    public class TokyoTyrantConnection : IDisposable {

        protected TcpClient _client;

        public TokyoTyrantConnection() {
            _client = new TcpClient();
        }

        #region ITokyoTyrantConnection Members

        public void Connect(string host, int port) {
            _client.NoDelay = true;
            _client.ReceiveBufferSize = 1024 * 100;
            _client.SendBufferSize = 1024 * 100;
            _client.Connect(host, port);
        }

        public void Disconnect() {
            _client.Client.Disconnect(true);
        }

        public bool Connected {
            get { return _client.Connected; }
        }

        #endregion

        enum Command : byte {
            PUT = 0x10,
            PUTKEEP = 0x11,
            PUTCAT = 0x12,
            PUTSHL = 0x13,
            PUTNR = 0x18,
            OUT = 0x20,
            GET = 0x30,
            MGET = 0x31,
            VSIZ = 0x38,
            ITERINIT = 0x50,
            ITERNEXT = 0x51,
            FWMKEYS = 0x58,
            ADDINT = 0x60,
            ADDDOUBLE = 0x61,
            EXT = 0x68,
            SYNC = 0x70,
            OPT = 0x71,
            VANISH = 0x72,
            COPY = 0x73,
            RESTORE = 0x74,
            SETMST = 0x78,
            RNUM = 0x80,
            SIZE = 0x81,
            STAT = 0x88,
            MISC = 0x90
        }

        public enum ScriptOption : int {
            RecordLocking = 1 << 0, // # scripting extension option: record locking
            GlobalLocking = 1 << 1 // # scripting extension option: global locking
        }

        public enum MiscOption : int {
            OmitLog = 1 << 0 // # versatile function option: omission of the update log
        }

        public enum IndexOption : int {
            ITLEXICAL = 0, // # index type: lexical string
            ITDECIMAL = 1, // index type: decimal string
            ITOPT = 9998, // # index type: optimize
            ITVOID = 9999, // # index type: void
            ITKEEP = 1 << 24 // # index type: keep existing index
        }


        /// <summary>
        /// Store a simple key/value record. 
        /// If a record with the same key exists in the database, it is overwritten if overwrite is true.
        /// </summary>
        /// <param name="key">The key</param>
        /// <param name="value">The value</param>
        /// <param name="overwrite">If false, this method will have effect if the key already exists</param>
        public void Put(string key, string value, bool overwrite) {
            using (var m = new MemoryStream()) {
                using (var writer = new BinaryWriter(m, Encoding.ASCII)) {
                    writer.Write((byte)0xC8);
                    writer.Write((overwrite ? (byte)Command.PUT : (byte)Command.PUTKEEP));
                    writer.Write(key.Length.AsBigEndian());
                    writer.Write(value.Length.AsBigEndian());
                    writer.Write(Encoding.ASCII.GetBytes(key));
                    writer.Write(Encoding.ASCII.GetBytes(value));
                    var stream = _client.GetStream(); // do not close this stream!
                    stream.Write(m.ToArray(), 0, (int)m.Length);
                    var reader = new BinaryReader(stream); // do not close this reader!
                    byte code = reader.ReadByte();
                    if (code != 0) {
                        throw new TTMiscException();
                    }
                }
            }
        }

        /// <summary>
        /// Concatenate a value at the end of the existing record.
        /// If there is no corresponding record, a new record is created.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="value">The value.</param>
        public void Concatenate(string key, string value) {
            using (var m = new MemoryStream()) {
                using (var writer = new BinaryWriter(m, Encoding.ASCII)) {
                    writer.Write((byte)0xC8);
                    writer.Write((byte)Command.PUTCAT);
                    writer.Write(key.Length.AsBigEndian());
                    writer.Write(value.Length.AsBigEndian());
                    writer.Write(Encoding.ASCII.GetBytes(key));
                    writer.Write(Encoding.ASCII.GetBytes(value));
                    var stream = _client.GetStream(); // do not close this stream!
                    stream.Write(m.ToArray(), 0, (int)m.Length);
                    var reader = new BinaryReader(stream); // do not close this reader!
                    byte code = reader.ReadByte();
                    if (code != 0) {
                        throw new TTMiscException();
                    }
                }
            }
        }

        /// <summary>
        /// Concatenate a value at the end of the existing record and shift it to the left.
        /// If there is no corresponding record, a new record is created.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="value">The value.</param>
        /// <param name="width"></param>
        public void ConcatenateShiftLeft(string key, string value, int width) {
            using (var m = new MemoryStream()) {
                using (var writer = new BinaryWriter(m, Encoding.ASCII)) {
                    writer.Write((byte)0xC8);
                    writer.Write((byte)Command.PUTSHL);
                    writer.Write(key.Length.AsBigEndian());
                    writer.Write(value.Length.AsBigEndian());
                    writer.Write(width.AsBigEndian());
                    writer.Write(Encoding.ASCII.GetBytes(key));
                    writer.Write(Encoding.ASCII.GetBytes(value));
                    var stream = _client.GetStream(); // do not close this stream!
                    stream.Write(m.ToArray(), 0, (int)m.Length);
                    var reader = new BinaryReader(stream); // do not close this reader!
                    byte code = reader.ReadByte();
                    if (code != 0) {
                        throw new TTMiscException();
                    }
                }
            }
        }

        /// <summary>
        /// Store a simple key/value record without getting a response from the server. 
        /// If a record with the same key exists in the database, it is overwritten.
        /// </summary>
        /// <param name="key">The key</param>
        /// <param name="value">The value</param>
        public void PutFast(string key, string value) {
            using (var m = new MemoryStream()) {
                using (var writer = new BinaryWriter(m, Encoding.ASCII)) {
                    writer.Write((byte)0xC8);
                    writer.Write((byte)Command.PUTNR);
                    writer.Write(key.Length.AsBigEndian());
                    writer.Write(value.Length.AsBigEndian());
                    writer.Write(Encoding.ASCII.GetBytes(key));
                    writer.Write(Encoding.ASCII.GetBytes(value));
                    var stream = _client.GetStream(); // do not close this stream!
                    stream.Write(m.ToArray(), 0, (int)m.Length);
                }
            }
        }

        /// <summary>
        /// Deletes a record with the specified key.
        /// </summary>
        /// <param name="key">The key.</param>
        public void Delete(string key) {
            using (var m = new MemoryStream()) {
                using (var writer = new BinaryWriter(m, Encoding.ASCII)) {
                    writer.Write((byte)0xC8);
                    writer.Write((byte)Command.OUT);
                    writer.Write(key.Length.AsBigEndian());
                    writer.Write(Encoding.ASCII.GetBytes(key));
                    var stream = _client.GetStream(); // do not close this stream!
                    stream.Write(m.ToArray(), 0, (int)m.Length);
                    var reader = new BinaryReader(stream); // do not close this reader!
                    byte code = reader.ReadByte();
                    if (code != 0) {
                        throw TokyoTyrantException.FromErrorCode(code);
                    }
                }
            }
        }


        /// <summary>
        /// Gets a record with the specified key.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns>Returns the value of the record as string.</returns>
        public string Get(string key) {
            using (var m = new MemoryStream()) {
                using (var writer = new BinaryWriter(m, Encoding.ASCII)) {
                    writer.Write((byte)0xC8);
                    writer.Write((byte)Command.GET);
                    writer.Write(key.Length.AsBigEndian());
                    writer.Write(Encoding.ASCII.GetBytes(key));
                    var stream = _client.GetStream(); // do not close this stream!
                    stream.Write(m.ToArray(), 0, (int)m.Length);
                    var reader = new BinaryReader(stream); // do not close this reader!
                    byte code = reader.ReadByte();
                    if (code == 0) {
                        int valSize = reader.ReadInt32().AsBigEndian();
                        if (valSize >= 0) {
                            return reader.ReadASCIIString(valSize);
                        } else
                            throw new TTReceiveException();
                    } else {
                        throw TokyoTyrantException.FromErrorCode(code);
                    }
                }
            }
        }

        /// <summary>
        /// Gets an integer record with the specified key.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns>Returns the value of the record as an integer.</returns>
        public int GetInteger(string key) {
            using (var m = new MemoryStream()) {
                using (var writer = new BinaryWriter(m, Encoding.ASCII)) {
                    writer.Write((byte)0xC8);
                    writer.Write((byte)Command.GET);
                    writer.Write(key.Length.AsBigEndian());
                    writer.Write(Encoding.ASCII.GetBytes(key));
                    var stream = _client.GetStream(); // do not close this stream!
                    stream.Write(m.ToArray(), 0, (int)m.Length);
                    var reader = new BinaryReader(stream); // do not close this reader!
                    byte code = reader.ReadByte();
                    if (code == 0) {
                        int valSize = reader.ReadInt32().AsBigEndian();
                        if (valSize == 4) {
                            return reader.ReadInt32();
                        } else
                            throw new TTReceiveException();
                    } else {
                        throw TokyoTyrantException.FromErrorCode(code);
                    }
                }
            }
        }

        /// <summary>
        /// Gets multiple records based on the list of keys.
        /// </summary>
        /// <param name="keys">The keys.</param>
        /// <returns>Returns a dictionary containing the records received.</returns>
        public IDictionary<string, string> GetMultiple(string[] keys) {
            Dictionary<string, string> result = new Dictionary<string, string>();
            using (var m = new MemoryStream()) {
                using (var writer = new BinaryWriter(m, Encoding.ASCII)) {
                    writer.Write((byte)0xC8);
                    writer.Write((byte)Command.MGET);
                    writer.Write(keys.Length.AsBigEndian());
                    foreach (var key in keys) {
                        writer.Write(key.Length.AsBigEndian());
                        writer.Write(Encoding.ASCII.GetBytes(key));
                    }
                    var stream = _client.GetStream(); // do not close this stream!
                    stream.Write(m.ToArray(), 0, (int)m.Length);
                    var reader = new BinaryReader(stream); // do not close this reader!
                    byte code = reader.ReadByte();
                    if (code == 0) {
                        int rnum = reader.ReadInt32().AsBigEndian();
                        if (rnum < 0) throw new TTReceiveException();
                        for (int i = 0; i < rnum; i++) {
                            int keySize = reader.ReadInt32().AsBigEndian();
                            int valSize = reader.ReadInt32().AsBigEndian();
                            if (keySize > 0 && valSize > 0) {
                                var keyStr = reader.ReadASCIIString(keySize);
                                var valStr = reader.ReadASCIIString(valSize);
                                result.Add(keyStr, valStr);
                            } else {
                                if (keySize < 0 || valSize < 0) {
                                    throw new TTReceiveException();
                                }
                            }
                        }
                    } else {
                        throw TokyoTyrantException.FromErrorCode(code);
                    }
                    return result;
                }
            }
        }

        /// <summary>
        /// Gets the size of the record's value.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns>Returns an integer specifying the record size</returns>
        public int GetSize(string key) {
            using (var m = new MemoryStream()) {
                using (var writer = new BinaryWriter(m, Encoding.ASCII)) {
                    writer.Write((byte)0xC8);
                    writer.Write((byte)Command.VSIZ);
                    writer.Write(key.Length.AsBigEndian());
                    writer.Write(Encoding.ASCII.GetBytes(key));
                    var stream = _client.GetStream(); // do not close this stream!
                    stream.Write(m.ToArray(), 0, (int)m.Length);
                    var reader = new BinaryReader(stream); // do not close this reader!
                    byte code = reader.ReadByte();
                    if (code == 0) {
                        return reader.ReadInt32().AsBigEndian();
                    } else {
                        throw TokyoTyrantException.FromErrorCode(code);
                    }
                }
            }
        }

        /// <summary>
        /// Initializes the iterator.
        /// The iterator is used in order to access the key of every record stored in a database.
        /// </summary>
        public void IteratorInitialize() {
            using (var m = new MemoryStream()) {
                using (var writer = new BinaryWriter(m, Encoding.ASCII)) {
                    writer.Write((byte)0xC8);
                    writer.Write((byte)Command.ITERINIT);
                    var stream = _client.GetStream(); // do not close this stream!
                    stream.Write(m.ToArray(), 0, (int)m.Length);
                    var reader = new BinaryReader(stream); // do not close this reader!
                    byte code = reader.ReadByte();
                    if (code != 0) {
                        throw TokyoTyrantException.FromErrorCode(code);
                    }
                }
            }
        }

        /// <summary>
        /// Get the next key of the iterator.
        /// If successful, the return value is the next key, else, it is null. 
        /// Null is returned when no record is to be get out of the iterator.
        /// It is possible to access every record by iteration of calling this method.  
        /// It is allowed to update or remove records whose keys are fetched while the iteration. 
        /// However, it is not assured if updating the database is occurred while the iteration. 
        /// Besides, the order of this traversal access method is arbitrary, so it is not assured that the order of storing matches the one of the traversal access.
        /// </summary>
        /// <returns>The key as a string or Null if no more records are to be found.</returns>
        public string IteratorNext() {
            using (var m = new MemoryStream()) {
                using (var writer = new BinaryWriter(m, Encoding.ASCII)) {
                    writer.Write((byte)0xC8);
                    writer.Write((byte)Command.ITERNEXT);
                    var stream = _client.GetStream(); // do not close this stream!
                    stream.Write(m.ToArray(), 0, (int)m.Length);
                    var reader = new BinaryReader(stream); // do not close this reader!
                    byte code = reader.ReadByte();
                    if (code == 0) {
                        int keySize = reader.ReadInt32().AsBigEndian();
                        if (keySize <= 0) {
                            return null;
                        } else
                            return reader.ReadASCIIString(keySize);
                    } else {
                        return null;
                    }
                }
            }
        }

        /// <summary>
        /// Gets the keys that start with the specified prefix.
        /// Note that this method may be very slow because every key in the database is scanned.
        /// </summary>
        /// <param name="prefix">The prefix.</param>
        /// <param name="max">The maximum number of keys to return. If it is negative, no limit is imposed.</param>
        /// <returns></returns>
        public string[] GetMatchingKeys(string prefix, int max) {
            IList<string> result = new List<string>();
            using (var m = new MemoryStream()) {
                using (var writer = new BinaryWriter(m, Encoding.ASCII)) {
                    writer.Write((byte)0xC8);
                    writer.Write((byte)Command.FWMKEYS);
                    writer.Write(prefix.Length.AsBigEndian());
                    writer.Write(max.AsBigEndian());
                    writer.Write(Encoding.ASCII.GetBytes(prefix));
                    var stream = _client.GetStream(); // do not close this stream!
                    stream.Write(m.ToArray(), 0, (int)m.Length);
                    var reader = new BinaryReader(stream); // do not close this reader!
                    byte code = reader.ReadByte();
                    if (code == 0) {
                        int knum = reader.ReadInt32().AsBigEndian();
                        if (knum < 0) throw new TTReceiveException();
                        for (int i = 0; i < knum; i++) {
                            int keySize = reader.ReadInt32().AsBigEndian();
                            if (keySize > 0) {
                                var keyStr = reader.ReadASCIIString(keySize);
                                result.Add(keyStr);
                            } else {
                                if (keySize < 0) {
                                    throw new TTReceiveException();
                                }
                            }
                        }
                    } else {
                        throw TokyoTyrantException.FromErrorCode(code);
                    }
                    return result.ToArray();
                }
            }
        }

        /// <summary>
        /// Increments an integer for the specified key. If the key does not exist, a new record is created and set to the specified value.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="value">The value.</param>
        /// <returns>The value of the newly incremented record.</returns>
        public int IncrementInteger(string key, int value) {
            using (var m = new MemoryStream()) {
                using (var writer = new BinaryWriter(m, Encoding.ASCII)) {
                    writer.Write((byte)0xC8);
                    writer.Write((byte)Command.ADDINT);
                    writer.Write(key.Length.AsBigEndian());
                    writer.Write(value.AsBigEndian());
                    writer.Write(Encoding.ASCII.GetBytes(key));
                    var stream = _client.GetStream(); // do not close this stream!
                    stream.Write(m.ToArray(), 0, (int)m.Length);
                    var reader = new BinaryReader(stream); // do not close this reader!
                    byte code = reader.ReadByte();
                    if (code == 0) {
                        return reader.ReadInt32().AsBigEndian();
                    } else
                        throw TokyoTyrantException.FromErrorCode(code);
                }
            }
        }

        /// <summary>
        /// Execute a lua script on the server and return the result as a string.
        /// </summary>
        /// <param name="name">The name of the script.</param>
        /// <param name="key">The key.</param>
        /// <param name="value">The value.</param>
        /// <param name="options">Locking options for the script, a bit map of the ScriptOptions enum </param>
        /// <returns></returns>
        public string ExecuteScript(string name, string key, string value, int options) {
            using (var m = new MemoryStream()) {
                using (var writer = new BinaryWriter(m, Encoding.ASCII)) {
                    writer.Write((byte)0xC8);
                    writer.Write((byte)Command.EXT);
                    writer.Write(name.Length.AsBigEndian());
                    writer.Write(options.AsBigEndian());
                    writer.Write(key.Length.AsBigEndian());
                    writer.Write(value.Length.AsBigEndian());
                    writer.Write(Encoding.ASCII.GetBytes(name));
                    writer.Write(Encoding.ASCII.GetBytes(key));
                    writer.Write(Encoding.ASCII.GetBytes(value));
                    var stream = _client.GetStream(); // do not close this stream!
                    stream.Write(m.ToArray(), 0, (int)m.Length);
                    var reader = new BinaryReader(stream); // do not close this reader!
                    byte code = reader.ReadByte();
                    if (code == 0) {
                        int valSize = reader.ReadInt32().AsBigEndian();
                        if (valSize >= 0) {
                            return reader.ReadASCIIString(valSize);
                        } else
                            throw new TTReceiveException();
                    } else {
                        throw TokyoTyrantException.FromErrorCode(code);
                    }
                }
            }
        }

        /// <summary>
        /// Synchronize updated contents with the file and the device.
        /// </summary>
        public void Synchronize() {
            using (var m = new MemoryStream()) {
                using (var writer = new BinaryWriter(m, Encoding.ASCII)) {
                    writer.Write((byte)0xC8);
                    writer.Write((byte)Command.SYNC);
                    writer.Flush();
                    var stream = _client.GetStream(); // do not close this stream!
                    stream.Write(m.ToArray(), 0, (int)m.Length);
                    var reader = new BinaryReader(stream); // do not close this reader!
                    byte code = reader.ReadByte();
                    if (code != 0) {
                        throw new TTMiscException();
                    }
                }
            }
        }

        /// <summary>
        /// Optimize the storage.
        /// </summary>
        /// <param name="parameters">specifies the string of the tuning parameters.</param>
        public void Optimize(string parameters) {
            using (var m = new MemoryStream()) {
                using (var writer = new BinaryWriter(m, Encoding.ASCII)) {
                    writer.Write((byte)0xC8);
                    writer.Write((byte)Command.OPT);
                    writer.Write(parameters.Length.AsBigEndian());
                    writer.Write(Encoding.ASCII.GetBytes(parameters));
                    var stream = _client.GetStream(); // do not close this stream!
                    stream.Write(m.ToArray(), 0, (int)m.Length);
                    var reader = new BinaryReader(stream); // do not close this reader!
                    byte code = reader.ReadByte();
                    if (code != 0) {
                        throw new TTMiscException();
                    }
                }
            }
        }

        /// <summary>
        /// Delete all records.
        /// </summary>
        public void Vanish() {
            using (var m = new MemoryStream()) {
                using (var writer = new BinaryWriter(m, Encoding.ASCII)) {
                    writer.Write((byte)0xC8);
                    writer.Write((byte)Command.VANISH);
                    var stream = _client.GetStream(); // do not close this stream!
                    stream.Write(m.ToArray(), 0, (int)m.Length);
                    var reader = new BinaryReader(stream); // do not close this reader!
                    byte code = reader.ReadByte();
                    if (code != 0) {
                        throw new TTMiscException();
                    }
                }
            }
        }

        /// <summary>
        /// Copy the database file.
        /// The database file is assured to be kept synchronized and not modified while the copying or executing operation is in progress.  
        /// So, this method is useful to create a backup file of the database file.
        /// </summary>
        /// <param name="path"> specifies the path of the destination file.  If it begins with `@', the trailing substring is executed as a command line.</param>
        public void Copy(string path) {
            using (var m = new MemoryStream()) {
                using (var writer = new BinaryWriter(m, Encoding.ASCII)) {
                    writer.Write((byte)0xC8);
                    writer.Write((byte)Command.COPY);
                    writer.Write(path.Length.AsBigEndian());
                    writer.Write(Encoding.ASCII.GetBytes(path));
                    var stream = _client.GetStream(); // do not close this stream!
                    stream.Write(m.ToArray(), 0, (int)m.Length);
                    var reader = new BinaryReader(stream); // do not close this reader!
                    byte code = reader.ReadByte();
                    if (code != 0) {
                        throw new TTMiscException();
                    }
                }
            }
        }

        /// <summary>
        /// Get the number of records in the database.
        /// </summary>
        /// <returns>Returns an Int64 specifying the number of records</returns>
        public Int64 GetRecordCount() {
            using (var m = new MemoryStream()) {
                using (var writer = new BinaryWriter(m, Encoding.ASCII)) {
                    writer.Write((byte)0xC8);
                    writer.Write((byte)Command.RNUM);
                    var stream = _client.GetStream(); // do not close this stream!
                    stream.Write(m.ToArray(), 0, (int)m.Length);
                    var reader = new BinaryReader(stream); // do not close this reader!
                    byte code = reader.ReadByte();
                    if (code == 0) {
                        int high = reader.ReadInt32().AsBigEndian();
                        int low = reader.ReadInt32().AsBigEndian();
                        Int64 result = (high << 32) + low;
                        return result;
                    } else {
                        throw TokyoTyrantException.FromErrorCode(code);
                    }
                }
            }
        }

        /// <summary>
        /// Get the size of the database.
        /// </summary>
        /// <returns>Returns an Int64 specifying the size of the database.</returns>
        public Int64 GetDatabaseSize() {
            using (var m = new MemoryStream()) {
                using (var writer = new BinaryWriter(m, Encoding.ASCII)) {
                    writer.Write((byte)0xC8);
                    writer.Write((byte)Command.SIZE);
                    var stream = _client.GetStream(); // do not close this stream!
                    stream.Write(m.ToArray(), 0, (int)m.Length);
                    var reader = new BinaryReader(stream); // do not close this reader!
                    byte code = reader.ReadByte();
                    if (code == 0) {
                        int high = reader.ReadInt32().AsBigEndian();
                        int low = reader.ReadInt32().AsBigEndian();
                        Int64 result = (high << 32) + low;
                        return result;
                    } else {
                        throw TokyoTyrantException.FromErrorCode(code);
                    }
                }
            }
        }


        /// <summary>
        /// Gets the database status as a dictionary.
        /// </summary>
        /// <returns>A dictionary that contains the data</returns>
        public IDictionary<string, string> GetDatabaseStatus() {
            using (var m = new MemoryStream()) {
                using (var writer = new BinaryWriter(m, Encoding.ASCII)) {
                    writer.Write((byte)0xC8);
                    writer.Write((byte)Command.STAT);
                    var stream = _client.GetStream(); // do not close this stream!
                    stream.Write(m.ToArray(), 0, (int)m.Length);
                    var reader = new BinaryReader(stream); // do not close this reader!
                    byte code = reader.ReadByte();
                    if (code == 0) {
                        int statSize = reader.ReadInt32().AsBigEndian();
                        if (statSize <= 0) {
                            return null;
                        } else {
                            var tsv = reader.ReadASCIIString(statSize);
                            Dictionary<string, string> result = new Dictionary<string, string>();
                            var lines = tsv.Split('\n');
                            foreach (var line in lines) {
                                var columns = line.Split('\t');
                                if (columns.Length >= 2) {
                                    result.Add(columns[0], columns[1]);
                                }
                            }
                            return result;
                        }
                    } else {
                        throw TokyoTyrantException.FromErrorCode(code);
                    }
                }
            }
        }

        /// <summary>
        /// Call a versatile function for miscellaneous operations.
        /// </summary>
        /// <param name="operation">Specifies the name of the function.  
        /// All databases support "putlist", "outlist", and "getlist". 
        /// "putlist" is to store records. It receives keys and values one after the other, and returns an empty list. 
        /// "outlist" is to remove records.  It receives keys, and returns an empty array.  
        /// "getlist" is to retrieve records.  It receives keys, and returns keys and values of corresponding records one after the other.  
        /// Table database supports "setindex", "search", and "genuid"</param>
        /// <param name="args">The arguments.</param>
        /// <param name="option">MiscOptions bit mask</param>
        /// <returns>Returns an array of the results.</returns>
        public string[] Misc(string operation, string[] args, MiscOption option) {
            List<string> result = new List<string>();
            using (var m = new MemoryStream()) {
                using (var writer = new BinaryWriter(m, Encoding.ASCII)) {
                    writer.Write((byte)0xC8);
                    writer.Write((byte)Command.MISC);
                    writer.Write(operation.Length.AsBigEndian());
                    writer.Write(((int)option).AsBigEndian());
                    writer.Write(args.Length.AsBigEndian());
                    writer.Write(Encoding.ASCII.GetBytes(operation));
                    foreach (string a in args) {
                        var val = a;
                        writer.Write(val.Length.AsBigEndian());
                        writer.Write(Encoding.ASCII.GetBytes(val));
                    }
                    var stream = _client.GetStream(); // do not close this stream!
                    stream.Write(m.ToArray(), 0, (int)m.Length);
                    var reader = new BinaryReader(stream); // do not close this reader!
                    byte code = reader.ReadByte();
                    if (code == 0) {
                        int rnum = reader.ReadInt32().AsBigEndian();
                        int size = 0;
                        for (int i = 0; i < rnum; i++) {
                            size = reader.ReadInt32().AsBigEndian();
                            if (size > 0) {
                                var item = reader.ReadASCIIString(size);
                                result.Add(item);
                            } else {
                                if (size < 0) {
                                    throw new TTReceiveException();
                                }
                            }
                        }
                    } else {
                        throw TokyoTyrantException.FromErrorCode(code);
                    }
                }
            }
            return result.ToArray();
        }

        /// <summary>
        /// Deletes multiple items with one call.
        /// </summary>
        /// <param name="keys">The keys.</param>
        public void DeleteMultiple(string[] keys) {
            Misc("outlist", keys, 0);
        }

        /// <summary>
        /// Adds multiple items with one call.
        /// </summary>
        /// <param name="items">The items.</param>
        public void PutMultiple(IDictionary<string, string> items) {
            IList<string> args = new List<string>();
            foreach (var k in items.Keys) {
                args.Add(k);
                args.Add(items[k]);
            }
            Misc("putlist", args.ToArray(), 0);
        }

        /// <summary>
        /// Adds a single record with multiple columns to a table database
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="column">The columns.</param>
        /// <param name="overwrite">If set to <c>true</c> overwrite.</param>
        public void PutColumns(string key, IDictionary<string, string> columns, bool overwrite) {
            List<string> args = new List<string>();
            args.Add(key);
            foreach (var k in columns.Keys) {
                args.Add(k);
                args.Add(columns[k]);
            }
            Misc((overwrite ? "put" : "putkeep"), args.ToArray(), 0);
        }

        void PutColumns(string key, string[] args, bool overwrite) {
            Misc((overwrite ? "put" : "putkeep"), args, 0);
        }

        /// <summary>
        /// Add an dictionary of column records. About twice as fast as calling PutColumns.
        /// </summary>
        /// <param name="records">The records.</param>
        /// <param name="overwrite">if set to <c>true</c> [overwrite].</param>
        public void PutColumnRecords(IDictionary<string, IDictionary<string, string>> records, bool overwrite) {

            var argList = new List<string[]>();
            foreach (var key in records.Keys) {
                var args = new List<string>();
                args.Add(key);
                var record = records[key];
                foreach (var k in record.Keys) {
                    args.Add(k);
                    args.Add(record[k]);
                }
                argList.Add(args.ToArray());
            }

            for (int i = 0; i < argList.Count; i++) {
                Misc((overwrite ? "put" : "putkeep"), argList[i], 0);
            }
        }

        /// <summary>
        /// Gets multiple records and their columns.
        /// </summary>
        /// <param name="keys">The keys.</param>
        /// <returns></returns>
        public IDictionary<string, IDictionary<string, string>> GetColumns(string[] keys) {
            var result = new Dictionary<string, IDictionary<string, string>>();
            var records = GetMultiple(keys);
            foreach (var key in records.Keys) {
                var values = ((string)records[key]).Split('\0');
                var col = new Dictionary<string, string>();
                int i = 0;
                while (i < values.Length - 1) {
                    col.Add(values[i], values[i + 1]);
                    i += 2;
                }
                result.Add(key, col);
            }
            return result;
        }

        /// <summary>
        /// Creates and index for a table database.
        /// </summary>
        /// <param name="name">The name of the column to index. If the index exists, it is rebuilt. An empty string means the primary key.</param>
        /// <param name="type">
        /// Specifies the index type: 
        /// `IndexOptions.ITLEXICAL' for lexical string, 
        /// `IndexOptions.ITDECIMAL' for decimal string.  
        /// `IndexOptions.ITOPT', the index is optimized.  
        /// `IndexOptions.ITVOID', the index is removed.  
        /// `IndexOptions.ITKEEP' is added by bitwise-or and the index exists, this method merely returns failure.
        /// </param>
        public void SetIndex(string name, TokyoTyrantConnection.IndexOption type) {
            List<string> args = new List<string>();
            args.Add(name);
            args.Add(((int)type).ToString());
            Misc("setindex", args.ToArray(), 0);
        }

        public string[] QueryKeys(TokyoQuery query) {
            var args = query.GetArgs();
            return Misc("search", args, MiscOption.OmitLog);
        }

        public void QueryDelete(TokyoQuery query) {
            List<string> argsList = query.GetArgs().ToList<string>();
            argsList.Add("out");
            Misc("search", argsList.ToArray(), 0);
        }


        public IDictionary<string, IDictionary<string, string>> QueryRecords(TokyoQuery query) {
            var result = new Dictionary<string, IDictionary<string, string>>();
            List<string> argsList = query.GetArgs().ToList<string>();
            argsList.Add("get");
            var args = Misc("search", argsList.ToArray(), MiscOption.OmitLog);
            foreach (var arg in args) {
                var col = new Dictionary<string, string>();
                var cary = arg.Split('\0');
                var cnum = cary.Length - 1;
                var i = 0;
                Dictionary<string, string> columns = null;
                while (i < cnum) {
                    var key = cary[i];
                    var value = cary[i + 1];
                    if (i == 0) {
                        columns = new Dictionary<string, string>();
                        result.Add(value, columns);
                    } else {
                        columns.Add(key, value);
                    }
                    i += 2;
                }
            }
            return result;
        }


        #region IDisposable Members

        public void Dispose() {
            if (Connected)
                Disconnect();
        }

        #endregion
    }

    public static class ProtocolHelper {

        public static int AsBigEndian(this int value) {
            if (!BitConverter.IsLittleEndian)
                return value;
            else
                return ProtocolHelper.SwapInt32(value);
        }

        public static Int64 AsBigEndian(this Int64 value) {
            if (!BitConverter.IsLittleEndian)
                return value;
            else
                return ProtocolHelper.SwapInt64(value);
        }


        public static string ReadASCIIString(this BinaryReader reader, int length) {
            var result = "";
            byte[] buf = new byte[length];
            var read = 0;
            var readCnt = 0;
            do {
                read = length - readCnt;
                read = reader.Read(buf, 0, read);
                result += System.Text.Encoding.ASCII.GetString(buf, 0, read);
                readCnt += read;
            } while (readCnt < length);
            return result;
        }


        public static short SwapInt16(short v) {
            return (short)(((v & 0xff) << 8) | ((v >> 8) & 0xff));
        }

        public static ushort SwapUInt16(ushort v) {
            return (ushort)(((v & 0xff) << 8) | ((v >> 8) & 0xff));
        }

        public static int SwapInt32(int v) {
            return (int)(((SwapInt16((short)v) & 0xffff) << 0x10) |
                          (SwapInt16((short)(v >> 0x10)) & 0xffff));
        }

        public static uint SwapUInt32(uint v) {
            return (uint)(((SwapUInt16((ushort)v) & 0xffff) << 0x10) |
                           (SwapUInt16((ushort)(v >> 0x10)) & 0xffff));
        }

        public static long SwapInt64(long v) {
            return (long)(((SwapInt32((int)v) & 0xffffffffL) << 0x20) |
                           (SwapInt32((int)(v >> 0x20)) & 0xffffffffL));
        }

        public static ulong SwapUInt64(ulong v) {
            return (ulong)(((SwapUInt32((uint)v) & 0xffffffffL) << 0x20) |
                            (SwapUInt32((uint)(v >> 0x20)) & 0xffffffffL));
        }

    }




}
